/**                   GNU GENERAL PUBLIC LICENSE
 *                       Version 3, 29 June 2007
 *
 * Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 * Everyone is permitted to copy and distribute verbatim copies
 * of this license document, but changing it is not allowed.
 * See License.txt file that comes with this distribution
 *
 *
 * Author: Vitaly German <vitaly.german@gmail.com>, (C) 2007-2008
 * 
 **/

#pragma once
#include <boost/shared_ptr.hpp>
#include "result_holder.hpp"


class OpExecutor {
public:
    typedef Result_aptr result_type;
    virtual result_type Fire(NumericResult&, NumericResult& )=0;
    virtual result_type Fire(NumericResult&, VarResult& )=0;
    virtual result_type Fire(VarResult&, NumericResult& )=0;
    virtual result_type Fire(VarResult&, VarResult& )=0;
    virtual ~OpExecutor(){}
};

typedef const boost::shared_ptr<OpExecutor> OpExecutor_aptr;
typedef OpExecutor * OpExecutor_ptr;


class Executor {
     OpExecutor_aptr real_executor_;
public:
   typedef OpExecutor::result_type result_type;

    Executor(const OpExecutor_ptr &re)
        :real_executor_(re){};

    result_type Fire(NumericResult& lhs, NumericResult & rhs ){
        return real_executor_->Fire(lhs, rhs);
    }
    result_type Fire(NumericResult&lhs, VarResult & rhs ){
        return real_executor_->Fire(lhs, rhs);
    }
    result_type Fire(VarResult& lhs, NumericResult &rhs){
        return real_executor_->Fire(lhs, rhs);
    }
    result_type Fire(VarResult& lhs, VarResult& rhs ){
        return real_executor_->Fire(lhs, rhs);
    }

    result_type OnError(Result & lhs, Result &rhs);

};

Executor::result_type Executor::OnError(Result&, Result&){
    throw (WrongTypeException());
    return Executor::result_type();
}


class Plus:public OpExecutor {
public:

    OpExecutor::result_type Fire(NumericResult&, NumericResult& );
    OpExecutor::result_type Fire(NumericResult&, VarResult& );
    OpExecutor::result_type Fire(VarResult&, NumericResult& );
    OpExecutor::result_type Fire(VarResult&, VarResult& );
    ~Plus(){}
};

class Minus:public OpExecutor {
public:
    OpExecutor::result_type Fire(NumericResult&, NumericResult& );
    OpExecutor::result_type Fire(NumericResult&, VarResult& );
    OpExecutor::result_type Fire(VarResult&, NumericResult& );
    OpExecutor::result_type Fire(VarResult&, VarResult& );
    ~Minus(){}
};

class Mult:public OpExecutor {
public:
    OpExecutor::result_type Fire(NumericResult&, NumericResult& );
    OpExecutor::result_type Fire(NumericResult&, VarResult& );
    OpExecutor::result_type Fire(VarResult&, NumericResult& );
    OpExecutor::result_type Fire(VarResult&, VarResult& );
    ~Mult(){}
};

class Div:public OpExecutor {
public:
    OpExecutor::result_type Fire(NumericResult&, NumericResult& );
    OpExecutor::result_type Fire(NumericResult&, VarResult& );
    OpExecutor::result_type Fire(VarResult&, NumericResult& );
    OpExecutor::result_type Fire(VarResult&, VarResult& );
    ~Div(){}
};



/// ------------- Plus ---------------------------------------------
OpExecutor::result_type Plus::Fire(NumericResult& n1, NumericResult& n2 ){
    rfm_solver::debug("\nNumericResult&, NumericResult&");
    return OpExecutor::result_type (new NumericResult(n1+n2));
}

OpExecutor::result_type Plus::Fire(NumericResult& n1, VarResult& n2 ){
    rfm_solver::debug("\nNumericResult&, VarResult&");
    VarResult_aptr res(new VarResult);
    res->update_const(n1, std::plus<Numeric>());
    res->merge_result(n2, std::plus<Numeric>());
    return res;
}

inline
OpExecutor::result_type Plus::Fire(VarResult&n1, NumericResult&n2 ){
    rfm_solver::debug("\nVarResult&, NumericResult&");
    return Fire(n2,n1);
}


OpExecutor::result_type Plus::Fire(VarResult& n1, VarResult& n2 ){
    rfm_solver::debug("\nVarResult&, VarResult&");
    VarResult_aptr res(new VarResult(n1));
    res->merge_result(n2, std::plus<Numeric>());
    return res;
}
/// ------------- Minus ---------------------------------------------
OpExecutor::result_type Minus::Fire(NumericResult& n1, NumericResult& n2 ){
    rfm_solver::debug("\nNumericResult&, NumericResult&");
    return OpExecutor::result_type (new NumericResult(n1-n2));
}

OpExecutor::result_type Minus::Fire(NumericResult& n1, VarResult& n2 ){
    rfm_solver::debug("\nNumericResult&, VarResult&");
    VarResult_aptr res(new VarResult);
    res->update_const(n1, std::plus<Numeric>());
    res->merge_result(n2, std::minus<Numeric>());
    return res;
}

OpExecutor::result_type Minus::Fire(VarResult& n1, NumericResult & n2 ){
    rfm_solver::debug("\nVarResult&, NumericResult&");
    VarResult_aptr res(new VarResult(n1));
    res->update_const(n2, std::minus<Numeric>());
    return res;
}


OpExecutor::result_type Minus::Fire(VarResult& n1, VarResult& n2 ){
    rfm_solver::debug("\nVarResult&, VarResult&");
    VarResult_aptr res(new VarResult(n1));
    res->merge_result(n2, std::minus<Numeric>());
    return res;
}

/// ------------- Mult ---------------------------------------------
OpExecutor::result_type Mult::Fire(NumericResult& n1, NumericResult& n2 ){
    rfm_solver::debug("\nNumericResult&, NumericResult&");
    return OpExecutor::result_type (new NumericResult(n1*n2));
}

OpExecutor::result_type Mult::Fire(NumericResult& n1, VarResult& n2 ){
     rfm_solver::debug("\nNumericResult&, VarResult&");
     VarResult_aptr res(new VarResult(n2));
     res->mult_result((Numeric)n1, std::multiplies<Numeric>());
     return res;
}

inline
OpExecutor::result_type Mult::Fire(VarResult& n1, NumericResult& n2){
    rfm_solver::debug("\nVarResult&, NumericResult&");
    return Fire(n2, n1);
}


OpExecutor::result_type Mult::Fire(VarResult& n1, VarResult& n2 ){
    rfm_solver::debug("\nVarResult&, VarResult&");
    throw(NonlinearExprException());
    return OpExecutor::result_type();
}

/// ------------- Div ---------------------------------------------
OpExecutor::result_type Div::Fire(NumericResult& n1, NumericResult& n2){
    rfm_solver::debug("\nNumericResult&, NumericResult&");
	if(n2 == 0) 
		throw DivByZero();
    return OpExecutor::result_type (new NumericResult(n1/n2));
}

OpExecutor::result_type Div::Fire(NumericResult& n1, VarResult& n2){
    rfm_solver::debug("\nNumericResult&, VarResult&");
    throw(NonlinearExprException());
    return OpExecutor::result_type();
}

OpExecutor::result_type Div::Fire(VarResult& n1, NumericResult& n2 ){
    rfm_solver::debug("\nVarResult&, NumericResult&");
	if(n2 == 0) 
		throw DivByZero();
    VarResult_aptr res(new VarResult(n1));
    res->mult_result(n2, std::divides<Numeric>());
    return res;
}

/// we could do basic checking if n1/n2 gives constant and not throw than

OpExecutor::result_type Div::Fire(VarResult& n1, VarResult& n2){
    rfm_solver::debug("\nVarResult&, VarResult&");
    throw(NonlinearExprException());
    return OpExecutor::result_type();
}
